/*
 * Copyright (C) 2014 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okio

import java.io.IOException
import okio.TestUtil.SEGMENT_SIZE
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Test

/**
 * Tests solely for the behavior of RealBufferedSink's implementation. For generic
 * BufferedSink behavior use BufferedSinkTest.
 */
class BufferedSinkJavaTest {
  @Test
  fun inputStreamCloses() {
    val sink = (Buffer() as Sink).buffer()
    val out = sink.outputStream()
    out.close()
    try {
      sink.writeUtf8("Hi!")
      fail()
    } catch (e: IllegalStateException) {
      assertEquals("closed", e.message)
    }
  }

  @Test
  fun bufferedSinkEmitsTailWhenItIsComplete() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
    assertEquals(0, sink.size)
    bufferedSink.writeByte(0)
    assertEquals(SEGMENT_SIZE.toLong(), sink.size)
    assertEquals(0, bufferedSink.buffer.size)
  }

  @Test
  fun bufferedSinkEmitMultipleSegments() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE * 4 - 1))
    assertEquals((SEGMENT_SIZE * 3).toLong(), sink.size)
    assertEquals((SEGMENT_SIZE - 1).toLong(), bufferedSink.buffer.size)
  }

  @Test
  fun bufferedSinkFlush() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeByte('a'.code)
    assertEquals(0, sink.size)
    bufferedSink.flush()
    assertEquals(0, bufferedSink.buffer.size)
    assertEquals(1, sink.size)
  }

  @Test
  fun bytesEmittedToSinkWithFlush() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("abc")
    bufferedSink.flush()
    assertEquals(3, sink.size)
  }

  @Test
  fun bytesNotEmittedToSinkWithoutFlush() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("abc")
    assertEquals(0, sink.size)
  }

  @Test
  fun bytesEmittedToSinkWithEmit() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("abc")
    bufferedSink.emit()
    assertEquals(3, sink.size)
  }

  @Test
  fun completeSegmentsEmitted() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE * 3))
    assertEquals((SEGMENT_SIZE * 3).toLong(), sink.size)
  }

  @Test
  fun incompleteSegmentsNotEmitted() {
    val sink = Buffer()
    val bufferedSink = (sink as Sink).buffer()
    bufferedSink.writeUtf8("a".repeat(SEGMENT_SIZE * 3 - 1))
    assertEquals((SEGMENT_SIZE * 2).toLong(), sink.size)
  }

  @Test
  fun closeWithExceptionWhenWriting() {
    val mockSink = MockSink()
    mockSink.scheduleThrow(0, IOException())
    val bufferedSink = mockSink.buffer()
    bufferedSink.writeByte('a'.code)
    try {
      bufferedSink.close()
      fail()
    } catch (expected: IOException) {
    }
    mockSink.assertLog("write([text=a], 1)", "close()")
  }

  @Test
  fun closeWithExceptionWhenClosing() {
    val mockSink = MockSink()
    mockSink.scheduleThrow(1, IOException())
    val bufferedSink = mockSink.buffer()
    bufferedSink.writeByte('a'.code)
    try {
      bufferedSink.close()
      fail()
    } catch (expected: IOException) {
    }
    mockSink.assertLog("write([text=a], 1)", "close()")
  }

  @Test
  fun closeWithExceptionWhenWritingAndClosing() {
    val mockSink = MockSink()
    mockSink.scheduleThrow(0, IOException("first"))
    mockSink.scheduleThrow(1, IOException("second"))
    val bufferedSink = mockSink.buffer()
    bufferedSink.writeByte('a'.code)
    try {
      bufferedSink.close()
      fail()
    } catch (expected: IOException) {
      assertEquals("first", expected.message)
    }
    mockSink.assertLog("write([text=a], 1)", "close()")
  }

  @Test
  fun operationsAfterClose() {
    val mockSink = MockSink()
    val bufferedSink = mockSink.buffer()
    bufferedSink.writeByte('a'.code)
    bufferedSink.close()

    // Test a sample set of methods.
    try {
      bufferedSink.writeByte('a'.code)
      fail()
    } catch (expected: IllegalStateException) {
    }
    try {
      bufferedSink.write(ByteArray(10))
      fail()
    } catch (expected: IllegalStateException) {
    }
    try {
      bufferedSink.emitCompleteSegments()
      fail()
    } catch (expected: IllegalStateException) {
    }
    try {
      bufferedSink.emit()
      fail()
    } catch (expected: IllegalStateException) {
    }
    try {
      bufferedSink.flush()
      fail()
    } catch (expected: IllegalStateException) {
    }

    // Test a sample set of methods on the OutputStream.
    val os = bufferedSink.outputStream()
    try {
      os.write('a'.code)
      fail()
    } catch (expected: IOException) {
    }
    try {
      os.write(ByteArray(10))
      fail()
    } catch (expected: IOException) {
    }

    // Permitted
    os.flush()
  }

  @Test
  fun writeAll() {
    val mockSink = MockSink()
    val bufferedSink = mockSink.buffer()
    bufferedSink.buffer.writeUtf8("abc")
    assertEquals(3, bufferedSink.writeAll(Buffer().writeUtf8("def")))
    assertEquals(6, bufferedSink.buffer.size)
    assertEquals("abcdef", bufferedSink.buffer.readUtf8(6))
    mockSink.assertLog() // No writes.
  }

  @Test
  fun writeAllExhausted() {
    val mockSink = MockSink()
    val bufferedSink = mockSink.buffer()
    assertEquals(0, bufferedSink.writeAll(Buffer()))
    assertEquals(0, bufferedSink.buffer.size)
    mockSink.assertLog() // No writes.
  }

  @Test
  fun writeAllWritesOneSegmentAtATime() {
    val write1 = Buffer().writeUtf8("a".repeat(SEGMENT_SIZE))
    val write2 = Buffer().writeUtf8("b".repeat(SEGMENT_SIZE))
    val write3 = Buffer().writeUtf8("c".repeat(SEGMENT_SIZE))
    val source = Buffer().writeUtf8(
      "" +
        "a".repeat(SEGMENT_SIZE) +
        "b".repeat(SEGMENT_SIZE) +
        "c".repeat(SEGMENT_SIZE),
    )
    val mockSink = MockSink()
    val bufferedSink = mockSink.buffer()
    assertEquals((SEGMENT_SIZE * 3).toLong(), bufferedSink.writeAll(source))
    mockSink.assertLog(
      "write(" + write1 + ", " + write1.size + ")",
      "write(" + write2 + ", " + write2.size + ")",
      "write(" + write3 + ", " + write3.size + ")",
    )
  }
}
